import { defineStore } from 'pinia';
import { DataNode } from 'ant-design-vue/es/tree';
import { type CnBlock } from '@/components/cn-editor/types';
import { useNoteApi } from '@/api/note/mods';
import { debounceWithMax } from '@/utils';
import { traverseTree } from '@/utils/treeUtil';

export type PersistNode = {
  parentNodeKey?: string;
  key: string;
  title: string;
  externalId: string;
  type: string;
  larkType?: string;
  isLeaf: boolean;
  children?: Array<string>;
};

export type NodeTreeState = {
  /**
   * 操作尽可能基于 key，维护一个 key 到数据的 Map，以便通过 key 获取节点数据
   * 但要注意这个 Map 可能内存泄露，比如其它客户端删除了节点
   * 当前客户端刷新目录下的文件时，如果发现少了，则需要将减少的节点及其子树节点从 Map 中清除
   * 当这个开销没有造成卡顿时，则直接遍历删除即可，如果造成卡顿了，可以考虑平时不清理
   * 对更新子节点的操作加一个较长延迟的防抖，在防抖任务中重新构建一下 Map 即可
   *
   * 为了清理 keyNodeMap 中的垃圾，唯一可以参照的就是 children，因为 children 每次都是新建的
   * 即 keyNodeMap 中的某一层的节点中，如果在这一层的 children 中没有，则说明已经被别的客户端删除了，需要清理
   * 因此，只有调用过 updateChildren 方法，才有必要对 keyNodeMap 进行清理，否则想清理也没有依据
   * 因为只有 updateChildren 方法会更新 children
   * 因此防抖清理方法在 updateChildren 中调用即可
   *
   * 由于 antd 树组件的 treeData 的 children 必须指向对象，不能指向 key
   * 因此无论如何，当某个节点改动，都会导致整个树响应式重新渲染
   * 本来手动循环 <tree-node> 元素可以避免这种情况，但 antd 新版本已弃用 <tree-node>
   * 因此暂时这样处理，如遇到卡顿再考虑优化
   *
   * 为了以 Entry 为粒度持久化 Record 数据，不能直接将 state 定义为 Record 类型
   * 因为那样在 pinia 插件中的 event.key 会是 Record 的 key，导致无法知道是对应哪个 state
   */
  keyNodeMap: Record<string, DataNode>;
  /**
   * 笔记块不作为节点属性，避免修改块造成响应式选人左侧节点树
   */
  keyBlocksMap: Record<string, Array<CnBlock>>;
  expandedKeys: Array<string>;
  selectedKeys: Array<string>;
};

export const STORE_KEY_NODE_TREE_CACHE = 'node-tree-cache';
export const ROOT_NOTE_KEY = '-1';
export const ROOT_NODE: DataNode = {
  title: '我的空间',
  type: 'NORMAL',
  key: ROOT_NOTE_KEY,
  isLeaf: false,
  children: [],
};
export const DEFAULT_KEY_NODE_MAP = { [ROOT_NOTE_KEY]: ROOT_NODE };
export const DEFAULT_KEY_BLOCKS_MAP = { [ROOT_NOTE_KEY]: [] };
export const DEFAULT_EXPANDED_KEYS = [ROOT_NOTE_KEY];

export const useNodeTreeCacheStore = defineStore(
  STORE_KEY_NODE_TREE_CACHE,
  () => {
    const keyNodeMap = ref<Record<string, DataNode>>(DEFAULT_KEY_NODE_MAP);
    const keyBlocksMap = ref<Record<string, Array<CnBlock>>>(DEFAULT_KEY_BLOCKS_MAP);
    const expandedKeys = ref<Array<string>>(DEFAULT_EXPANDED_KEYS);
    const selectedKeys = ref<Array<string>>([]);

    const treeData = computed((): Array<DataNode> => {
      return [keyNodeMap.value[ROOT_NOTE_KEY]];
    });
    const selectedKey = computed((): string => {
      const selectedKeys_ = selectedKeys.value;
      if (!selectedKeys_ || selectedKeys_.length < 1) {
        return '';
      }
      return selectedKeys_[0];
    });
    const selectedNode = computed((): DataNode | undefined => {
      const selectedKey_ = selectedKey.value;
      return selectedKey_ ? keyNodeMap.value[selectedKey_] : undefined;
    });
    const blocksOfSelectedNote = computed((): Array<CnBlock> => {
      const selectedKey_ = selectedKey.value;
      if (!selectedKey_) {
        return [];
      }
      return keyBlocksMap.value[selectedKey_] ?? [];
    });
    const breadCrumbs = computed((): Array<DataNode> => {
      let node: DataNode | undefined = selectedNode.value;
      if (!node) {
        return [];
      }
      const breadCrumbs_: Array<DataNode> = [];
      do {
        breadCrumbs_.push(node);
        node = keyNodeMap.value[node.parentNodeKey];
      } while (node);
      breadCrumbs_.reverse();
      return breadCrumbs_;
    });

    const hsetAndPersistKeyNodeMap = (key: string, node: DataNode) => {
      keyNodeMap.value[key] = node;
    };
    const hsetAndPersistKeyBlocksMap = (key: string, cnBlocks: Array<CnBlock>) => {
      keyBlocksMap.value[key] = cnBlocks;
    };
    // 选中节点，更新笔记块
    const selectNode = (dataNode: DataNode) => {
      const selectedKey_: string = dataNode.key as string;
      const selectedNode = keyNodeMap.value[selectedKey_];
      if (selectedNode) {
        if (selectedNode.type == 'LARK_DOCUMENT') {
          // 异步更新笔记的块
          const { noteApi } = useNoteApi();
          noteApi.note.getLarkBlocks(selectedKey_).then(data => {
            store.hsetAndPersistKeyBlocksMap(selectedKey_, data as Array<CnBlock>);
          });
        }
      }
    };
    // 展开或收起节点
    const expandNode = ({ node, expanded }: { node: DataNode; expanded: boolean }) => {
      const key_ = node.key as string;
      if (expanded) {
        // 更新子节点数据
        const { noteApi } = useNoteApi();
        switch (node.type) {
          case 'LARK_FOLDER':
            // 飞书文件夹
            noteApi.note.getLarkChildren(node.externalId).then(larkPage => {
              // TODO 暂未考虑分页，当数据量大于每页数量时，这个算法是有问题的
              const larkFiles: Array<note.LarkFile> = larkPage.files ?? [];
              updateChildren(
                larkFiles,
                larkFile => {
                  const isFolder = larkFile.type == 'folder';
                  return {
                    /**
                     * 不要使用 parentNode，否则会与 children 一起导致递归引用
                     * 用 parentNodeKey + keyNodeMap 方便构建面包屑即可
                     * node.parentNode -> parentNode.children -> node.parentNode ...
                     */
                    parentNodeKey: node.key,
                    key: larkFile.token ?? '',
                    title: larkFile.name ?? '',
                    externalId: larkFile.token ?? '',
                    type: isFolder ? 'LARK_FOLDER' : 'LARK_DOCUMENT',
                    larkType: larkFile.type,
                  };
                },
                node,
              );
            });
            break;
          case 'NORMAL':
            // 普通笔记
            noteApi.note.getChildren(Number(key_)).then(notes => {
              updateChildren(
                notes,
                note => {
                  return {
                    /**
                     * 不要使用 parentNode，否则会与 children 一起导致递归引用
                     * 用 parentNodeKey + keyNodeMap 方便构建面包屑即可
                     * node.parentNode -> parentNode.children -> node.parentNode ...
                     */
                    parentNodeKey: node.key,
                    key: String(note.id ?? '-1'),
                    title: note.name ?? '',
                    externalId: note.externalId ?? '',
                    type: note.type,
                  };
                },
                node,
              );
            });
            break;
          default:
            break;
        }
      }
    };
    const updateChildren = <T>(datas: Array<T>, converter: (t: T) => DataNode, parentNode: DataNode) => {
      const newParentChildren: Array<DataNode> = [];
      datas.forEach(data => {
        const node: DataNode = converter(data);
        node.isLeaf = false;
        const newKey: string = node.key as string;
        const oldNode = keyNodeMap.value[newKey];
        // 只更新当前层的节点数据，当前层节点的子节点不变，这里不算更新 children，children 没有变化
        node.children = oldNode ? oldNode.children : [];
        newParentChildren.push(node);
        store.hsetAndPersistKeyNodeMap(newKey, node);
        if (!keyBlocksMap.value[newKey]) {
          // 节点的旧的块不需要变化，如果是新节点，没有块，则设置为空数组，避免空值判断
          store.hsetAndPersistKeyBlocksMap(newKey, []);
        }
      });

      /**
       * 更新父节点的 children
       * 这里不能直接用 parentNode.children = newChildren; 否则无法触发计算属性
       * 因为 parentNode 是 a-tree 组件弹出事件中的对象，而这个对象并非缓存中的同一个对象
       */
      changeChildren(parentNode.key as string, newParentChildren);
    };
    // children 改变时统一调用这个方法，以便垃圾回收
    const changeChildren = (key: string, newChildren: Array<DataNode>) => {
      const node_ = keyNodeMap.value[key];
      node_.children = newChildren;
      store.hsetAndPersistKeyNodeMap(key, node_);
      // 防抖清理垃圾
      debouncedClear();
    };
    // 延迟 1 分钟的防抖垃圾回收，防止内存泄露，如果被调用了 100 次都没有执行则直接执行，不再延迟
    const debouncedClear = debounceWithMax(
      () => {
        const newKeyNodeMap: Record<string, DataNode> = {};
        traverseTree(keyNodeMap.value[ROOT_NOTE_KEY], node => {
          newKeyNodeMap[node.key] = node;
        });
        keyNodeMap.value = newKeyNodeMap;
      },
      60000,
      100,
    );

    const store = {
      keyNodeMap,
      keyBlocksMap,
      expandedKeys,
      selectedKeys,
      treeData,
      selectedKey,
      selectedNode,
      blocksOfSelectedNote,
      breadCrumbs,
      hsetAndPersistKeyNodeMap,
      hsetAndPersistKeyBlocksMap,
      selectNode,
      expandNode,
      updateChildren,
      changeChildren,
    };

    return store;
  },
  {
    cnPersist: {
      states: {
        expandedKeys: {},
        selectedKeys: {},
        keyNodeMap: {
          policy: 'HASH',
          serialize: newValue => {
            if (!newValue) {
              return null;
            }
            const value_: DataNode = newValue as DataNode;
            const persistNode: PersistNode = {
              key: value_.key as string,
              title: value_.title ?? '',
              externalId: value_.externalId ?? '',
              type: value_.type ?? '',
              isLeaf: value_.isLeaf ?? false,
            };
            if (value_.larkType) {
              persistNode.larkType = value_.larkType;
            }
            const oldChildren = value_.children;
            if (oldChildren && oldChildren.length > 0) {
              persistNode.children = oldChildren.map(child => {
                return child.key as string;
              });
            }
            return JSON.stringify(persistNode);
          },
          deserializePostHandler: newValue => {
            const newValue_: Record<string, PersistNode> = newValue as Record<string, PersistNode>;
            Object.values(newValue_).forEach(value_ => {
              const children_: Array<string> | undefined = value_.children;
              if (children_ && children_.length > 0) {
                value_.children = children_.map(childKey => {
                  return newValue_[childKey];
                }) as unknown as Array<string>;
              }
            });
            return newValue;
          },
        },
      },
    },
  },
);

export type NodeTreeStore = ReturnType<typeof useNodeTreeCacheStore>;
